#pragma once

namespace sml{
    Document compile(const Binary&);
}
namespace sml::compiler{
    struct Location{
        uint _row;
        uint _col;
        Location(uint row=0, uint col=0):_row(row),_col(col){}
        Location& operator=(const Location& b) noexcept{
            _row = b._row;
            _col = b._col;
            return *this;
        }
    };
    inline auto row(const Location& a) noexcept{return a._row;}
    inline auto& row(Location& a) noexcept{return a._row;}
    inline auto col(const Location& a) noexcept{return a._col;}
    inline auto& col(Location& a) noexcept{return a._col;}
    inline ostream& operator<<(ostream& str, const Location& a){
        return str << row(a) << ':' << col(a);
    }

    struct Token{
        enum Type{
            L0,         // (
            R0,         // )
            L1,         // [
            R1,         // ]
            L2,         // {
            R2,         // }
            ID,
            STRING,
            INTEGER,
            REAL,
            BLOB,
            AT,         // @
            SLASH,      // /
            KEY_NIL,    // nil
            KEY_TRUE,   // true
            KEY_FALSE,  // false
            UNKNOWN,
        };
        const Location  _loc;
        const Type      _type = UNKNOWN;
        const string    _text;
        Token(const Location& loc, Type type)
            :_loc(loc),_type(type),_text(){}
        Token(const Location& loc, Type type, string_view text)
            :_loc(loc),_type(type),_text(text){}
        Token& operator=(const Token& a){
            const_cast<Location&>(_loc) = a._loc;
            const_cast<Type&>(_type) = a._type;
            const_cast<string&>(_text) = a._text;
            return *this;
        }
    };
    inline const auto& loc(const Token& a) noexcept{return a._loc;}
    inline auto row(const Token& a) noexcept{return row(a._loc);}
    inline auto col(const Token& a) noexcept{return col(a._loc);}
    inline auto type(const Token& a) noexcept{return a._type;}
    inline string_view text(const Token& a) noexcept{return a._text;}
    inline bool operator==(const Token& a, Token::Type b_type) noexcept{return type(a) == b_type;}
    inline bool operator==(const Token& a, string_view b_text) noexcept{return text(a) == b_text;}
    inline string blob_text(const Token& a) noexcept{
        auto text_len = size(text(a));
        if (text_len < 3){return "0B";}
        auto byte_n = uint(text_len / 3);
        if (byte_n >= 1047586){return String(uint(byte_n/1024/1024), "MB");}
        if (byte_n >= 1024){return String(uint(byte_n/1024), "KB");}
        return String(byte_n, "B");
    }
    ostream& operator<<(ostream& str, Token::Type ttype){
        switch (ttype){
        case Token::L0: str << "'('"; break;
        case Token::L1: str << "'['"; break;
        case Token::L2: str << "'{'"; break;
        case Token::R2: str << "'}'"; break;
        case Token::R1: str << "']'"; break;
        case Token::R0: str << "')'"; break;
        case Token::ID: str << "ID"; break;
        case Token::STRING: str << "STRING"; break;
        case Token::INTEGER: str << "INTEGER"; break;
        case Token::REAL: str << "REAL"; break;
        case Token::BLOB: str << "BLOB"; break;
        case Token::AT: str << "'@'"; break;
        case Token::SLASH: str << "'/'"; break;
        case Token::KEY_NIL: str << "NIL"; break;
        case Token::KEY_TRUE: str << consts::KEY_TRUES[0]; break;
        case Token::KEY_FALSE: str << consts::KEY_FALSES[0]; break;
        default:assert(!"incorrect porogram-logic");break;
        }
        return str;
    }
    ostream& operator<<(ostream& str, const Token& t){
        str << loc(t) << ' ' << type(t);
        switch (type(t)){
        case Token::ID:
        case Token::INTEGER:
        case Token::REAL: str << ':' << text(t); break;
        case Token::STRING: str << ':' << Quoted(text(t)); break;
        case Token::BLOB: str << ':' << blob_text(t); break;
        default:break;
        }
        return str;
    }

    struct Error : sml::Error{
        using sml::Error::Error;
        explicit Error(const fs::path& path, const Location& loc, string_view what)
            :sml::Error::Error(String('[', path, ' ', loc, "] ", what)){}
    };

    struct Context{
        Document&           doc;    // output
        const Binary&       source;
        Context(Document& output, const Binary& source)
            :doc(output),source(source){}
    };

    Document& build(Context&);
}
namespace sml::compiler::lex{
    struct Error : compiler::Error{using compiler::Error::Error;};
    struct Board;
    unique_ptr<Board> make(Context&);
    Token get(Board&);
}
namespace sml::compiler::syntax{
    struct Error : compiler::Error{using compiler::Error::Error;};
    Context& pass(Context&);
}
namespace sml::compiler::calc_constexpr{
    struct Error : compiler::Error{using compiler::Error::Error;};
    Context& pass(Context& ctx);
}

namespace sml{
Document compile(const Binary& bin){
    logger::debug("sml::compile");
    auto doc = (path(bin) == consts::BINARY_FROM_MEMORY) ?
        Document() : Document(path(bin).filename());
    compiler::Context ctx(doc, bin);
    return Document(std::move(compiler::build(ctx)));
}}
namespace sml::compiler{
Document& build(Context& ctx){
    logger::debug("sml::compiler::build");
    return (calc_constexpr::pass
            (syntax::pass
             (ctx))).doc;
}}

namespace sml::compiler::lex{
struct Board{
    const Binary&   source;
    bool            ready;
    Token           cur_token;
    size_t          pos = 0;
    uint            loc_row = 1;
    uint            loc_col = 1;
    Location        loc_cur_token;
    Board(Context& ctx):source(ctx.source)
        ,ready(size(ctx.source) > 0)
        ,cur_token(Location(0,0), Token::UNKNOWN){}
};

inline const char current(Board& ctx, int offset=0) noexcept{
    if (ctx.pos+offset < size(ctx.source)){
        return ctx.source[ctx.pos+offset];
    }
    return char(0);
}
Board& next(Board& ctx, uint n=1) noexcept{
    if (n > 0){
        if (ctx.pos+1 < size(ctx.source)){
            if (current(ctx) == '\n'){
                ctx.loc_row++;
                ctx.loc_col = 1;
            }else{
                ctx.loc_col++;
            }
            ctx.pos++;
            return next(ctx, n-1);
        }else{
            ctx.pos = size(ctx.source);
            ctx.ready = false;
        }
    }
    return ctx;
}
inline Board& commit(Board& ctx, Token::Type type, const string& text={}){
    ctx.cur_token = Token{
        ctx.loc_cur_token,
        type,
        text};
    return ctx;
}
inline Board& save_loc(Board& ctx) noexcept{
    ctx.loc_cur_token = Location(ctx.loc_row, ctx.loc_col);
    return ctx;
}
Board& _Symbol(Board& ctx, Token::Type t){
    return commit(next(ctx), t);
}
Board& _Real(Board& ctx, string& text){
    text += '.';
    while (next(ctx).ready){
        switch (current(ctx)){
        case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
            text += current(ctx); break;
        case '.': throw Error(String("the '.' can once only in number: ", text));
        case 'i': throw Error(String("the 'i' for Integer only, but it's real: ", text));
        case 'f': next(ctx); [[fallthrough]];
        default: return commit(ctx, Token::REAL, text);
        }
    }
    return commit(ctx, Token::REAL, text);
}
Board& _Integer(Board& ctx, char first){
    string text(1, first);
    while (next(ctx).ready){
        switch (current(ctx)){
        case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
            text += current(ctx); break;
        case '.': return _Real(ctx, text);
        case 'f': return commit(next(ctx), Token::REAL, text+".0");
        case 'i': next(ctx); [[fallthrough]];
        default: return commit(ctx, Token::INTEGER, text);
        }
    }
    return commit(ctx, Token::INTEGER, text);
}
tuple<Token::Type,string_view> map_id(string_view text){
    if (text == consts::KEY_NIL){return {Token::KEY_NIL, {}};}
    for (const auto& key : consts::KEY_TRUES){
        if (text == key){return {Token::KEY_TRUE, {}};}
    }
    for (const auto& key : consts::KEY_FALSES){
        if (text == key){return {Token::KEY_FALSE, {}};}
    }
    return {Token::ID, text};
}
Board& _Id(Board& ctx, char first){
    string text(1, first);
    auto Take = [&]()->Board&{
        const auto [_type, _text_sw] = map_id(text);
        return commit(ctx, _type, string(_text_sw));
    };
    while (next(ctx).ready){
        switch (current(ctx)){
        case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x20:
        case '(':
        case '[':
        case '{':
        case '}':
        case ']':
        case ')':
        case '@':
        case '/':
        case '"': case '\'':
        case '#':
            return Take();
        default:
            if (size(text) < consts::NAME_LEN_MAX){
                text += current(ctx);break;
            }else{throw Error(String("len of name has over max len: ", consts::NAME_LEN_MAX, "byte"));}
        }
    }
    return Take();
}
Board& _SymbolMinus(Board& ctx){
    switch (current(ctx,1)){
    case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
        return _Integer(ctx, '-');
    case char(0): case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x20:
    case '(': case '[': case '{': case '}': case ']': case ')':
    case '@': case '/': case '-': case '"': case '\'': case '<': case '#':
        return commit(next(ctx), Token::ID, "-");
    default: return _Id(ctx, '-');
    }
}
uint _String_EscapeChar(Board& ctx, ostream& str){
    const auto c = current(ctx);
    switch (c){
    case 't': str << '\t'; return 1;
    case 'n': str << '\n'; return 1;
    case 'r': str << '\r'; return 1;
    default: str << c; return 1;
    }
}
Board& _String(Board& ctx, char sym){
    assert(sym != '\\');
    auto len = consts::STRING_LEN_MAX;
    stringstream ss;
    while (next(ctx).ready){
        const auto c = current(ctx);
        if (c == sym){break;}
        else if (c == '\\'){
            len -= _String_EscapeChar(next(ctx), ss);
        }
        else{ss << c; len -= 1;}
        if (len < 1){
            throw Error(String("len of string has over max len: ", consts::STRING_LEN_MAX, "byte"));
        }
    }
    return commit(next(ctx), Token::STRING, ss.str());
}
void _Blob_Byte_SkipWhites(Board& ctx){
    switch (current(ctx)){
    case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x20:
        _Blob_Byte_SkipWhites(next(ctx));
    default: return;
    }
}
void _Blob_Byte_Hex(char c, ostream& str){
    switch (c){
    case char(0):
        throw Error("here need a hex-char but has eof");
    case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
        str << c; break;
    case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': 
    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
        str << char(std::toupper(c)); break;
    default: throw Error(String("non a hex-char: ", Quoted(string_view(&c,1), '\'')));
    }
}
void _Blob_Byte(Board& ctx, ostream& str){
    _Blob_Byte_SkipWhites(ctx);
    _Blob_Byte_Hex(current(ctx), str);
    _Blob_Byte_Hex(current(next(ctx)), str);
    _Blob_Byte_SkipWhites(ctx);
}
Board& _Blob(Board& ctx){
    auto len = consts::BLOB_LEN_MAX;
    stringstream ss;
    while (next(ctx).ready){
        if ('>' == current(ctx)){break;}
        _Blob_Byte(ctx, (ss << ' '));
        if (--len < 1){
            throw Error(String("len of blob has over max len: ", consts::BLOB_LEN_MAX, "byte"));
        }
    }
    return commit(next(ctx), Token::BLOB, ss.str());
}
Board& _Comment(Board& ctx){
    // Multi-Line
    if (current(ctx,1) == '#'){
        next(ctx);
        while (next(ctx).ready){
            if (current(ctx) == '\n' and
                current(ctx,1) == '#' and
                current(ctx,2) == '#'){
                return next(ctx,3);
            }
        }
    }
    // Single-Line
    while(next(ctx).ready){
        if (current(ctx) == '\n'){break;}
    }
    return ctx;
}
Board& first(Board& ctx){
    switch (current(save_loc(ctx))){
    case char(0): return ctx;
    case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x20:
        return first(next(ctx));
    case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
        return _Integer(ctx, current(ctx));
    case '(': return _Symbol(ctx,Token::L0);
    case '[': return _Symbol(ctx,Token::L1);
    case '{': return _Symbol(ctx,Token::L2);
    case '}': return _Symbol(ctx,Token::R2);
    case ']': return _Symbol(ctx,Token::R1);
    case ')': return _Symbol(ctx,Token::R0);
    case '@': return _Symbol(ctx,Token::AT);
    case '/': return _Symbol(ctx,Token::SLASH);
    case '-': return _SymbolMinus(ctx);
    case '"': return _String(ctx, '"');
    case '\'': return _String(ctx, '\'');
    case '<': return _Blob(ctx);
    case '#': return first(_Comment(ctx));
    default:
        return _Id(ctx, current(ctx));
    }
}

unique_ptr<Board> make(Context& ctx){
    auto board = make_unique<Board>(ctx);
    return board;
}
Token get(Board& ctx) try{
    return first(ctx).cur_token;
}catch (Error& e){
    throw Error(path(ctx.source), Location(ctx.loc_row, ctx.loc_col), String("lex: ", e.what()));
}}

namespace sml::compiler::syntax{
struct Board;
using SyntaxResolver = function<Board&(Board&,const Token&)>;
struct Board{
    Document&                   doc;
    vector<SyntaxResolver>      scope;
    vector<Node*>               scope_node;
    vector<unique_ptr<Any>>     scope_obj;
    Board(Context& ctx):doc(ctx.doc){}
};
#define SYNTAX(NAME)            Board& NAME(Board& ctx, const Token& t)
#define FORWARD_SYNTAX(NAME)    SYNTAX(NAME)
#define shift(CTX,NAME)         NAME(CTX, t)
#define shift_top(CTX)          parse(CTX, t)
#define to_shift(CTX,TO)        shift(to(CTX, TO), TO)
#define to_shift2(CTX,TO,SHIFT) shift(to(CTX, TO), SHIFT)

inline auto& push_top(Board& ctx){
    stack_push(ctx.scope, back(ctx.scope));
    return ctx;
}
inline auto& push(Board& ctx, SyntaxResolver f){
    stack_push(ctx.scope, f);
    return ctx;
}
inline auto& pop(Board& ctx){
    stack_pop(ctx.scope);
    return ctx;
}
inline auto& to(Board& ctx, SyntaxResolver f){
    stack_top(ctx.scope) = f;
    return ctx;
}
inline Board& parse(Board& ctx, const Token& t){
    return stack_top(ctx.scope)(ctx, t);
}

inline Node& node_top(Board& ctx) noexcept{
    assert(! is_empty(ctx.scope_node));
    return *(stack_top(ctx.scope_node));
}
inline Board& node_push(Board& ctx, const AtomName& name){
    if (size(ctx.scope_node) < consts::NODE_DEPTH_MAX){
        const Node& n = add(node_top(ctx), name);
        stack_push(ctx.scope_node, (Node*)&n);
        return ctx;
    }else{throw Error(String("this node has over max depth: ", consts::NODE_DEPTH_MAX));}
}
inline Board& node_pop(Board& ctx){
    assert(! is_empty(ctx.scope_node));
    stack_pop(ctx.scope_node);
    return ctx;
}
template <typename T=Any>
inline ObjectOf<T> obj_top(Board& ctx) noexcept{
    assert(! is_empty(ctx.scope_obj));
    return ObjectOf<T>(back(ctx.scope_obj).get());
}
template <typename T, typename... ARGS>
inline Board& obj_push(Board& ctx, ARGS&&... args){
    ctx.scope_obj.emplace_back(make_unique<T>(std::forward<ARGS>(args)...));
    return ctx;
}
inline Board& obj_pop(Board& ctx){
    pop_back(ctx.scope_obj);
    return ctx;
}

FORWARD_SYNTAX(_Const);
FORWARD_SYNTAX(_Value);

FORWARD_SYNTAX(_RefValuePathSlash);
SYNTAX(_RefValuePath){
    logger::debug("syntax::_RefValuePath");
    if (t == Token::ID){
        if (AtomName::is_valid(text(t))){
            auto& path = nodepath(obj_top<RefValue>(ctx));
            path << AtomName(text(t), true);
            return to(ctx, _RefValuePathSlash);
        }else{
            Error("not a valid name as part of const-path");
        }
    }
    throw Error("here need a name as part of const-path");
}
SYNTAX(_RefValuePathSlash){
    logger::debug("syntax::_RefValuePathSlash");
    if (t == Token::SLASH){
        return to(ctx, _RefValuePath);
    }
    return shift_top(pop(ctx));
}
// Mode: ?
// Case: <Path>
//       <PathText>
SYNTAX(_RefValue){
    logger::debug("syntax::_RefValue");    
    auto& path = nodepath(obj_top<RefValue>(obj_push<RefValue>(ctx, AtomNodePath())));
    if (t == Token::ID){
        return shift(ctx, _RefValuePath);
    }else if (t == Token::SLASH){
        type(path) = AtomNodePath::ABSOLUTE;
        return to(ctx, _RefValuePath);
    }else if (t == Token::STRING){
        const auto items = split(text(t), '/');
        for (const auto& part : items){
            _RefValuePath(ctx, Token(loc(t), Token::ID, part));
        }
        return pop(ctx);
    }
    throw Error("incorrect const-path");
}
// Mode: '@' ?
// Case: <RefValue>
SYNTAX(_ConstRef){
    logger::debug("syntax::_ConstRef");
    switch (type(t)){
    case Token::ID:
    case Token::STRING:
    case Token::SLASH:
        return shift(ctx, _RefValue);
    default:
        throw Error("can be ref <Path> only");
    }
}
FORWARD_SYNTAX(_ConstListItems);
SYNTAX(_ConstListItems_Take){
    logger::debug("syntax::_ConstListItems_Take");
    auto item = obj_top(ctx)->Copy();
    auto list = obj_top<ConstList>(obj_pop(ctx));
    *list << std::move(item);
    return to_shift(ctx, _ConstListItems);
}
// Mode: '[' <Const> ?
// Case: <Const>* ']'
SYNTAX(_ConstListItems){
    logger::debug("syntax::_ConstListItems");
    if (t == Token::R1){
        return pop(ctx);
    }
    return to_shift2(ctx, _ConstListItems_Take,_Const);
}
// Mode: '[' ?
// Case: <Const>
SYNTAX(_ConstList){
    logger::debug("syntax::_ConstList");
    obj_push<ConstList>(ctx);
    return to_shift(ctx, _ConstListItems);
}
// Mode: ?
// Case: <Nil>
//       <Bool>
//       <Int>
//       <Real>
//       <ConstText>
//       <ConstList>
//       <ConstRef>
SYNTAX(_Const){
    logger::debug("syntax::_Const");
    switch (type(t)){
    case Token::KEY_NIL:
        return obj_push<Nil>(ctx);
    case Token::KEY_TRUE:
        return obj_push<Bool>(ctx, true);
    case Token::KEY_FALSE:
        return obj_push<Bool>(ctx, false);
    case Token::INTEGER:
        return obj_push<Int>(ctx, AtomInt(text(t)));
    case Token::REAL:
        return obj_push<Real>(ctx, AtomReal(text(t)));
    case Token::STRING:
        return obj_push<ConstString>(ctx, text(t));
    case Token::BLOB:
        return obj_push<Blob>(ctx, to_bytes(split_ignore_empty(text(t), ' ')));
    case Token::AT:
        return to(push_top(ctx), _ConstRef);
    case Token::L1:
        return to(push_top(ctx), _ConstList);
    default:
        throw Error("incorrect const value");
    }
}

FORWARD_SYNTAX(_Node);
// Mode: { <Name> <Value> ?...
// Case: {}... }
SYNTAX(_NodeChilds){
    logger::debug("syntax::_NodeChilds");
    if (t == Token::L2){
        return push(ctx, _Node);
    }else if (t == Token::R2){
        return pop(node_pop(ctx));
    }
    throw Error("here can be child-node only");
}
SYNTAX(_NodeValue_Take){
    logger::debug("syntax::_NodeValue_Take");
    value(node_top(ctx)) = obj_top(ctx)->Copy();
    return to_shift(obj_pop(ctx), _NodeChilds);
}
// Mode: '{' <Name> ?
// Case: <Node>* '}'
//       <Const> <Node>* '}'
SYNTAX(_NodeValue){
    logger::debug("syntax::_NodeValue");
    if (t == Token::R2){
        return pop(node_pop(ctx));
    }else if (t == Token::L2){
        return push(to(ctx, _NodeChilds), _Node);
    }
    return to_shift2(ctx, _NodeValue_Take, _Const);
}
// Mode: '{' ?
// Case: <Name>
SYNTAX(_Node){
    logger::debug("syntax::_Node");
    switch (type(t)){
    case Token::ID:
    case Token::STRING:
        if (AtomName::is_valid(text(t))){
            logger::debug(String("level:", size(ctx.scope_node), " begin Node ", Quoted(text(t))));
            return to(node_push(ctx, AtomName(text(t), true)), _NodeValue);
        }else{
            throw Error("not a valid node-name");
        }
    default: throw Error("node must have a name");
    }
}
// Mode: ... ?
// Case: <Node>*
SYNTAX(_Toplevel){
    logger::debug("syntax::_Toplevel");
    if (t == Token::L2){
        return push(ctx, _Node);
    }
    // Ignore others
    return ctx;
}

unique_ptr<Board> make(Context& ctx){
    logger::debug("sml::compiler::syntax::make");
    auto board = make_unique<Board>(ctx);
    stack_push(board->scope_node, static_cast<Node*>(&ctx.doc));
    push(*board, _Toplevel);
    return board;
}
Context& pass(Context& ctx){
    logger::debug("sml::compiler::syntax::pass");
    const Location* cur_loc = nullptr;
    try{
        auto lex_board = lex::make(ctx);
        auto board = make(ctx);
        while (lex_board->ready){
            const Token& token = lex::get(*lex_board);
            if (token == Token::UNKNOWN){break;}
            cur_loc = &(loc(token));
            parse(*board, token);
        }
    }catch (Error& e){
        throw Error(path(ctx.source), *cur_loc, String("syntax: ", e.what()));
    }
    return ctx;
}}

namespace sml::compiler::calc_constexpr{
namespace _calc_refvalue{
    Node* pass(Node* root){
        return root;
    }
}
Context& pass(Context& ctx){
    Node* root = &(ctx.doc);
    _calc_refvalue::pass(root);
    return ctx;
}}

